package za.co.chaotik.tron.screens.renderer;

import java.util.List;

import za.co.chaotik.tron.AbstractMap;
import za.co.chaotik.tron.Cycle;
import za.co.chaotik.tron.CycleGame;
import za.co.chaotik.tron.Player;
import za.co.chaotik.tron.Position;
import za.co.chaotik.tron.exceptions.LostException;
import za.co.chaotik.tron.exceptions.TronException;
import za.co.chaotik.tron.patterns.Subject;
import za.co.chaotik.tron.screens.CycleGameScreen;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Event;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;

public class SphericalMap2DRenderer extends MapRenderer {
	private ShapeRenderer shaper;
	private Stage stage;
	private AbstractMap map;
	private int delay = 0;
	private static final String LOG = CycleGame.class.getSimpleName();

	public SphericalMap2DRenderer(CycleGameScreen screen) {
		super(screen);
		
		float w = Gdx.graphics.getWidth();
		float h = Gdx.graphics.getHeight();
		
		shaper = new ShapeRenderer();
		
		stage = new Stage(w, h, false);
	}
	
	@Override
	public InputProcessor getInputProcessor() {
		return stage;
	}

	@Override
	public void hasChanged(Subject subject) {
		// Subject should be a map of some sort
		map = (AbstractMap) subject;
		
		// Update view state after the map has changed
		//// Remove all existing actors from the stage
		stage.clear();
		
		//// Create actors to represent cycles
		for (Cycle cycle : map.getCycles()) {
			final Cycle c = cycle;
			
			Actor actor = new Actor() {
				@Override
				public Actor hit(float x, float y) {
					return null;
				}
				
				@Override
				public void draw(SpriteBatch batch, float parentAlpha) {
					float minY = Gdx.graphics.getHeight() * 0.1f;
					float maxY = Gdx.graphics.getHeight() * 0.9f;
					float minX = Gdx.graphics.getWidth() * 0.1f;
					float maxX = Gdx.graphics.getWidth() * 0.9f;
					
					shaper.begin(ShapeType.FilledCircle);
					
					float yPos = minY + (maxY - minY) / (map.getHeight() - 1) * c.getY();
					float xPos;
					
					if ((c.getY() == 0) || (c.getY() == map.getHeight() - 1)) {
						// horizontally centered if it's at a pole
						xPos = minX + (maxX - minX) / 2.0f;
					} else {
						xPos = minX + (maxX - minX) / (map.getWidth() - 1) * c.getX();
					}
						
					shaper.setColor(c.getOwner().getColor());
					shaper.filledCircle(xPos, yPos, (maxY - minY) / map.getHeight() / 4);
					
					shaper.end();
				}
			};
			
			stage.addActor(actor);
		}
		
		//// Create actors to represent possible moves for human players
		//// Create actors to represent cycles
		if (map.getCurrentPlayer().getAI() == null) {
			for (Position position : map.getValidMoves(map.getCycle(map.getCurrentPlayer()))) {
				final Position pos = position;
				
				float minY = Gdx.graphics.getHeight() * 0.1f;
				float maxY = Gdx.graphics.getHeight() * 0.9f;
				float minX = Gdx.graphics.getWidth() * 0.1f;
				float maxX = Gdx.graphics.getWidth() * 0.9f;
				float yPos = minY + (maxY - minY) / (map.getHeight() - 1) * pos.getY();;
				float xPos;
				
				if ((pos.getY() == 0) || (pos.getY() == map.getHeight() - 1)) {
					// horizontally centered if it's a pole
					xPos = minX + (maxX - minX) / 2.0f;
				} else {
					xPos = minX + (maxX - minX) / (map.getWidth() - 1) * pos.getX();
				}
				
				Actor actor = new Actor() {
					@Override
					public Actor hit(float x, float y) {
						if ((Math.abs(x) >= 0) && (Math.abs(x) <= this.getWidth()) && 
								(Math.abs(y) >= 0) && (Math.abs(y) <= this.getHeight())) {
							//Gdx.app.log(LOG, "Hit at: (" + x + ", " + y + ").");
							return this;
						}
						
						return null;
					}
					
					@Override
					public void draw(SpriteBatch batch, float parentAlpha) {
						Gdx.gl.glLineWidth(3.0f);
						shaper.begin(ShapeType.Circle);
						
						shaper.setColor(Color.WHITE);
						shaper.circle(this.getX(), this.getY(), this.getWidth());
						
						shaper.end();
						Gdx.gl.glLineWidth(1.0f);
					}
				};

				actor.setX(xPos);
				actor.setY(yPos);
				actor.setWidth((maxY - minY) / map.getHeight() / 4);
				actor.setHeight((maxY - minY) / map.getHeight() / 4);
				
				actor.addListener(new EventListener() {
					
					@Override
					public boolean handle(Event event) {
						//Gdx.app.log(LOG, "Received event " + event.toString());
						InputEvent evt = (InputEvent) event;
						
						if (evt.getType().equals(InputEvent.Type.touchDown)) {
							Cycle cycle = map.getCycle(map.getCurrentPlayer());
							
							//event.stop();
							
							try {
								map.moveCycle(cycle, map.getPosition(pos.getX(), pos.getY()));
							} catch (LostException e) {
								Gdx.app.log(LOG, e.getMessage());
								
								getScreen().getGame().showScreen("GameOver");
							} catch (TronException e) {
								Gdx.app.log(LOG, e.getMessage());
							}
							
							return true;
						}
						
						return false;
					}
				});
				
				stage.addActor(actor);
			}
		}
	}

	public void render(float delta) {
		// Update
		if (map.getCurrentPlayer().getAI() != null) {
			try {
				map.getCurrentPlayer().getAI().playTurn();
			} catch (LostException e) {
				Gdx.app.log(LOG, e.getMessage());
				
				getScreen().getGame().showScreen("GameOver");
			} catch (TronException e) {
				Gdx.app.log(LOG, e.getMessage(), e);
			}
		}
		
		// Clear the screen to black
		Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		
		// Draw the map
		//// Draw the grid
		shaper.begin(ShapeType.Line);
		shaper.setColor(0.4f, 0.4f, 0.4f, 1.0f);
		
		float minY = Gdx.graphics.getHeight() * 0.1f;
		float maxY = Gdx.graphics.getHeight() * 0.9f;
		float minX = Gdx.graphics.getWidth() * 0.1f;
		float maxX = Gdx.graphics.getWidth() * 0.9f;
		float oneSquareY = (maxY - minY) / map.getHeight();
		float centerX = minX + (maxX - minX) / 2.0f;
			
		for (int y = 0; y < map.getHeight(); y++) {
			float yPos = minY + (maxY - minY) / (map.getHeight() - 1) * y;
			
			if ((y != 0) && (y != map.getHeight() - 1)) {
				shaper.line(minX, yPos, maxX, yPos);
			}
			
			for (int x = 0; x < map.getWidth(); x++) {
				float xPos; 
				if (y == 0) { 
					// Render the north pole
					xPos = minX + (maxX - minX) / (map.getWidth() - 1) * x;
					shaper.line(centerX, minY, xPos, minY + oneSquareY);
				} else if (y == map.getHeight() - 1) {
					// Render the south pole
					xPos = minX + (maxX - minX) / (map.getWidth() - 1) * x;
					shaper.line(centerX, maxY, xPos, maxY - oneSquareY);
				} else {
					xPos = minX + (maxX - minX) / (map.getWidth() - 1) * x;
					shaper.line(xPos, minY + oneSquareY, xPos, maxY - oneSquareY);
				}
			}
		}
		
		shaper.end();
		
		// Draw the walls
		Gdx.gl.glLineWidth(3.0f);
		for (Player player : map.getPlayers()) {
			shaper.begin(ShapeType.Line);
			shaper.setColor(player.getColor());
			
			List<Position> moves = map.getMoves(player);
			for (int i = 1; i < moves.size(); i++) {
				Position previous = moves.get(i - 1);
				Position next = moves.get(i);
				
				float dist = previous.distance(next);
				float y1 = minY + (maxY - minY) / (map.getHeight() - 1) * previous.getY();
				float x1 = minX + (maxX - minX) / (map.getWidth() - 1) * previous.getX();
				float y2 = minY + (maxY - minY) / (map.getHeight() - 1) * next.getY();
				float x2 = minX + (maxX - minX) / (map.getWidth() - 1) * next.getX();
				
				// Don't draw a wall when the player wraps moves from E-W or W-E on the edges
				// of the map
				if ((previous.getY() == 0) || (previous.getY() == map.getHeight() - 1)) {
					// centering for the poles
					x1 = minX + (maxX - minX) / 2.0f;
					shaper.line(x1, y1, x2, y2);
				} else if ((next.getY() == 0) || (next.getY() == map.getHeight() - 1)) {
					// centering for the poles
					x2 = minX + (maxX - minX) / 2.0f;
					shaper.line(x1, y1, x2, y2);
				} else if (dist <= 1.0f) {
					shaper.line(x1, y1, x2, y2);
				}
			}
			
			shaper.end();
		}
		Gdx.gl.glLineWidth(1.0f);
		
		//// Draw the cycles, walls and movement interface
		stage.draw();
	}
}
